{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8db4ada5ce6ebf73",
   "metadata": {},
   "source": [
    "### Numpy 加速\n",
    "Python 是一种相对较慢的编程语言，但是我们可以通过使用Numpy来加速Python的运算。Numpy是一个基于C语言的库，提供了许多高效的运算函数，例如矩阵运算和线性代数运算等。这些运算都基于C语言实现，因此速度非常快。\n",
    "\n",
    "GP的性能瓶颈通常在于模型评估。因此，在这里，我们重点关注如何加速评估函数。其实很简单，将数据集转换为Numpy数组，然后使用Numpy函数来计算MSE即可。下面是一个例子。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "initial_id",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-14T09:14:24.923043100Z",
     "start_time": "2023-11-14T09:14:24.908046400Z"
    }
   },
   "outputs": [],
   "source": [
    "import time\n",
    "\n",
    "import numpy as np\n",
    "from deap import base, creator, tools, gp\n",
    "\n",
    "\n",
    "# 符号回归\n",
    "def evalSymbReg(individual, pset):\n",
    "    # 编译GP树为函数\n",
    "    func = gp.compile(expr=individual, pset=pset)\n",
    "    \n",
    "    # 使用numpy创建一个向量\n",
    "    x = np.linspace(-10, 10, 100) \n",
    "    \n",
    "    # 评估生成的函数并计算MSE\n",
    "    mse = np.mean((func(x) - x**2)**2)\n",
    "    \n",
    "    return (mse,)\n",
    "\n",
    "# 创建个体和适应度函数\n",
    "creator.create(\"FitnessMin\", base.Fitness, weights=(-1.0,))\n",
    "creator.create(\"Individual\", gp.PrimitiveTree, fitness=creator.FitnessMin)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3d94e424b58af5a",
   "metadata": {},
   "source": [
    "同时，我们还可以考虑将一些算子替换为Numpy函数。尽管这并不是非常重要，因为Numpy已经重载了许多运算符。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "cb6cf38094256262",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-14T09:14:24.933166800Z",
     "start_time": "2023-11-14T09:14:24.927568400Z"
    }
   },
   "outputs": [],
   "source": [
    "import random\n",
    "\n",
    "# 定义函数集合和终端集合\n",
    "pset = gp.PrimitiveSet(\"MAIN\", arity=1)\n",
    "pset.addPrimitive(np.add, 2)\n",
    "pset.addPrimitive(np.subtract, 2)\n",
    "pset.addPrimitive(np.multiply, 2)\n",
    "pset.addPrimitive(np.negative, 1)\n",
    "def random_int(): return random.randint(-1, 1)\n",
    "pset.addEphemeralConstant(\"rand101\", random_int)\n",
    "pset.renameArguments(ARG0='x')\n",
    "\n",
    "# 定义遗传编程操作\n",
    "toolbox = base.Toolbox()\n",
    "toolbox.register(\"expr\", gp.genHalfAndHalf, pset=pset, min_=1, max_=2)\n",
    "toolbox.register(\"individual\", tools.initIterate, creator.Individual, toolbox.expr)\n",
    "toolbox.register(\"population\", tools.initRepeat, list, toolbox.individual)\n",
    "toolbox.register(\"compile\", gp.compile, pset=pset)\n",
    "toolbox.register(\"evaluate\", evalSymbReg, pset=pset)\n",
    "toolbox.register(\"select\", tools.selTournament, tournsize=3)\n",
    "toolbox.register(\"mate\", gp.cxOnePoint)\n",
    "toolbox.register(\"mutate\", gp.mutUniform, expr=toolbox.expr, pset=pset)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e09fa8e7890d583b",
   "metadata": {},
   "source": [
    "现在，让我们来测试一下加速效果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "88c62bc071d56191",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-14T09:14:25.525098600Z",
     "start_time": "2023-11-14T09:14:24.935256200Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   \t      \t                    fitness                    \t                      size                     \n",
      "   \t      \t-----------------------------------------------\t-----------------------------------------------\n",
      "gen\tnevals\tavg    \tgen\tmax   \tmin\tnevals\tstd    \tavg    \tgen\tmax\tmin\tnevals\tstd    \n",
      "0  \t300   \t3231.36\t0  \t153712\t0  \t300   \t12338.5\t3.90333\t0  \t7  \t2  \t300   \t1.61884\n",
      "1  \t266   \t2585.69\t1  \t153712\t0  \t266   \t8907.64\t4      \t1  \t11 \t2  \t266   \t1.82392\n",
      "2  \t278   \t3919.23\t2  \t159956\t0  \t278   \t17641.7\t4.21   \t2  \t11 \t2  \t278   \t1.74907\n",
      "3  \t265   \t43227.2\t3  \t1.23323e+07\t0  \t265   \t710749 \t4.31667\t3  \t12 \t2  \t265   \t1.88053\n",
      "4  \t273   \t3408.05\t4  \t153712     \t0  \t273   \t17494.5\t4.15   \t4  \t13 \t2  \t273   \t1.77036\n",
      "5  \t264   \t5199.33\t5  \t157978     \t0  \t264   \t24605.9\t4.08667\t5  \t13 \t3  \t264   \t1.82185\n",
      "6  \t280   \t45092.2\t6  \t1.23323e+07\t0  \t280   \t710911 \t3.67333\t6  \t13 \t3  \t280   \t1.46514\n",
      "7  \t277   \t1893.94\t7  \t153712     \t0  \t277   \t15176.7\t3.32   \t7  \t13 \t3  \t277   \t1.09739\n",
      "8  \t270   \t521.6  \t8  \t8359.39    \t0  \t270   \t1702.78\t3.35   \t8  \t9  \t2  \t270   \t1.01694\n",
      "9  \t287   \t1458.88\t9  \t153712     \t0  \t287   \t12557.2\t3.39   \t9  \t9  \t2  \t287   \t1.09144\n",
      "10 \t279   \t3098.08\t10 \t153712     \t0  \t279   \t19695.4\t3.32   \t10 \t9  \t2  \t279   \t0.961388\n",
      "time: 0.22003555297851562\n",
      "multiply(x, x)\n",
      "   \t      \t                    fitness                    \t                      size                     \n",
      "   \t      \t-----------------------------------------------\t-----------------------------------------------\n",
      "gen\tnevals\tavg   \tgen\tmax   \tmin\tnevals\tstd    \tavg \tgen\tmax\tmin\tnevals\tstd    \n",
      "0  \t300   \t3263.5\t0  \t153712\t0  \t300   \t12445.2\t3.99\t0  \t7  \t2  \t300   \t1.61552\n",
      "1  \t283   \t3426.33\t1  \t153712\t0  \t283   \t15161.3\t3.99\t1  \t12 \t2  \t283   \t1.87525\n",
      "2  \t265   \t3897.27\t2  \t153712\t0  \t265   \t17426.4\t4.29667\t2  \t13 \t2  \t265   \t1.89085\n",
      "3  \t275   \t8211.55\t3  \t153781\t0  \t275   \t30924.6\t4.63333\t3  \t15 \t2  \t275   \t2.17383\n",
      "4  \t266   \t7778.7 \t4  \t608604\t0  \t266   \t43356.7\t4.67667\t4  \t16 \t2  \t266   \t2.56101\n",
      "5  \t286   \t213662 \t5  \t4.87163e+07\t0  \t286   \t2.88552e+06\t4.71667\t5  \t15 \t2  \t286   \t2.3909 \n",
      "6  \t269   \t10094.3\t6  \t174559     \t0  \t269   \t36608.7    \t4.11667\t6  \t13 \t2  \t269   \t1.82658\n",
      "7  \t284   \t43648.5\t7  \t1.17257e+07\t0  \t284   \t676052     \t3.76   \t7  \t13 \t2  \t284   \t1.67005\n",
      "8  \t271   \t2856.37\t8  \t164153     \t0  \t271   \t19939.5    \t3.5    \t8  \t13 \t3  \t271   \t1.43178\n",
      "9  \t276   \t918.294\t9  \t153712     \t0  \t276   \t9001.51    \t3.48667\t9  \t13 \t2  \t276   \t1.41061\n",
      "10 \t276   \t2592.24\t10 \t153712     \t0  \t276   \t17624.9    \t3.50667\t10 \t11 \t3  \t276   \t1.3354 \n",
      "time: 0.17170000076293945\n",
      "multiply(x, x)\n",
      "   \t      \t                    fitness                    \t                     size                     \n",
      "   \t      \t-----------------------------------------------\t----------------------------------------------\n",
      "gen\tnevals\tavg    \tgen\tmax   \tmin\tnevals\tstd    \tavg \tgen\tmax\tmin\tnevals\tstd   \n",
      "0  \t300   \t3270.77\t0  \t153712\t0  \t300   \t12401.9\t4.01\t0  \t7  \t2  \t300   \t1.6703\n",
      "1  \t279   \t2583.82\t1  \t157909\t0  \t279   \t9053.5 \t4.23667\t1  \t11 \t2  \t279   \t1.9098\n",
      "2  \t274   \t2162.42\t2  \t18766.1\t0  \t274   \t1669.48\t4.39   \t2  \t13 \t2  \t274   \t2.09712\n",
      "3  \t278   \t2481.21\t3  \t151631 \t0  \t278   \t8776.53\t4.90667\t3  \t14 \t2  \t278   \t2.50691\n",
      "4  \t269   \t7575.61\t4  \t642041 \t0  \t269   \t43379.4\t5.18667\t4  \t15 \t2  \t269   \t2.69787\n",
      "5  \t275   \t46096.7\t5  \t1.17217e+07\t0  \t275   \t675766 \t5.6    \t5  \t18 \t2  \t275   \t2.97546\n",
      "6  \t279   \t11086.2\t6  \t1.36468e+06\t0  \t279   \t88197.2\t5.71   \t6  \t18 \t2  \t279   \t2.98762\n",
      "7  \t275   \t46473.6\t7  \t1.24839e+07\t0  \t275   \t719604 \t6.14   \t7  \t17 \t2  \t275   \t3.27522\n",
      "8  \t271   \t47475.7\t8  \t1.17217e+07\t0  \t271   \t676585 \t5.43   \t8  \t16 \t2  \t271   \t2.81989\n",
      "9  \t275   \t12306.6\t9  \t608604     \t0  \t275   \t50448.7\t5.14667\t9  \t17 \t2  \t275   \t2.8528 \n",
      "10 \t273   \t50938.9\t10 \t1.21786e+07\t0  \t273   \t702962 \t4.88   \t10 \t20 \t2  \t273   \t2.85638\n",
      "time: 0.18781375885009766\n",
      "multiply(x, x)\n"
     ]
    }
   ],
   "source": [
    "import numpy\n",
    "from deap import algorithms\n",
    "\n",
    "# 定义统计指标\n",
    "stats_fit = tools.Statistics(lambda ind: ind.fitness.values)\n",
    "stats_size = tools.Statistics(len)\n",
    "mstats = tools.MultiStatistics(fitness=stats_fit, size=stats_size)\n",
    "mstats.register(\"avg\", numpy.mean)\n",
    "mstats.register(\"std\", numpy.std)\n",
    "mstats.register(\"min\", numpy.min)\n",
    "mstats.register(\"max\", numpy.max)\n",
    "\n",
    "# 使用默认算法\n",
    "np_time=[]\n",
    "for i in range(3):\n",
    "    start=time.time()\n",
    "    population = toolbox.population(n=300)\n",
    "    hof = tools.HallOfFame(1)\n",
    "    pop, log  = algorithms.eaSimple(population=population,\n",
    "                               toolbox=toolbox, cxpb=0.9, mutpb=0.1, ngen=10, stats=mstats, halloffame=hof, verbose=True)\n",
    "    end=time.time()\n",
    "    print('time:',end-start)\n",
    "    np_time.append(end-start)\n",
    "    print(str(hof[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be250c9740bc2817",
   "metadata": {},
   "source": [
    "对比下面的原始评估函数，使用Numpy的加速效果还是非常明显的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f2ddb57d24051753",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-14T09:14:27.572677800Z",
     "start_time": "2023-11-14T09:14:25.521044600Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   \t      \t                    fitness                    \t                      size                     \n",
      "   \t      \t-----------------------------------------------\t-----------------------------------------------\n",
      "gen\tnevals\tavg    \tgen\tmax   \tmin\tnevals\tstd    \tavg    \tgen\tmax\tmin\tnevals\tstd    \n",
      "0  \t300   \t5101.77\t0  \t608247\t0  \t300   \t37075.7\t4.11667\t0  \t7  \t2  \t300   \t1.67821\n",
      "1  \t278   \t3934.9 \t1  \t157817\t0  \t278   \t17563.9\t4.62333\t1  \t13 \t2  \t278   \t1.94802\n",
      "2  \t282   \t3837.35\t2  \t153622\t0  \t282   \t17495.9\t5.05667\t2  \t14 \t2  \t282   \t2.17718\n",
      "3  \t269   \t48570.9\t3  \t1.17166e+07\t0  \t269   \t676326 \t5.36333\t3  \t14 \t2  \t269   \t2.32049\n",
      "4  \t267   \t8871.01\t4  \t157817     \t0  \t267   \t33190.2\t5.77   \t4  \t15 \t2  \t267   \t2.52529\n",
      "5  \t280   \t8154.67\t5  \t153622     \t0  \t280   \t32084.7\t5.89667\t5  \t19 \t2  \t280   \t2.54676\n",
      "6  \t267   \t45572.2\t6  \t1.17166e+07\t0  \t267   \t675571 \t5.97   \t6  \t17 \t2  \t267   \t2.68125\n",
      "7  \t275   \t48064.9\t7  \t1.23228e+07\t0  \t275   \t710493 \t6.15333\t7  \t17 \t2  \t275   \t2.63119\n",
      "8  \t269   \t10362.2\t8  \t153691     \t0  \t269   \t37077  \t6.18   \t8  \t17 \t2  \t269   \t2.92363\n",
      "9  \t265   \t10860.4\t9  \t153622     \t0  \t265   \t38092.7\t6.25667\t9  \t19 \t2  \t265   \t3.11086\n",
      "10 \t272   \t167779 \t10 \t4.74663e+07\t0  \t272   \t2.73557e+06\t6.33667\t10 \t19 \t3  \t272   \t3.22542\n",
      "time: 0.7642621994018555\n",
      "   \t      \t                    fitness                    \t                     size                     \n",
      "   \t      \t-----------------------------------------------\t----------------------------------------------\n",
      "gen\tnevals\tavg    \tgen\tmax   \tmin\tnevals\tstd    \tavg    \tgen\tmax\tmin\tnevals\tstd   \n",
      "0  \t300   \t4083.92\t0  \t608247\t0  \t300   \t34966.1\t3.91667\t0  \t7  \t2  \t300   \t1.6441\n",
      "1  \t269   \t3263.59\t1  \t153622\t0  \t269   \t15073.4\t4.05667\t1  \t13 \t2  \t269   \t1.85116\n",
      "2  \t272   \t4977.16\t2  \t157817\t0  \t272   \t21479.6\t4.58667\t2  \t13 \t2  \t272   \t2.08066\n",
      "3  \t279   \t4169.28\t3  \t157817\t0  \t279   \t19541.1\t4.81333\t3  \t17 \t2  \t279   \t2.40939\n",
      "4  \t271   \t6204.82\t4  \t157817\t0  \t271   \t26128.3\t4.89333\t4  \t16 \t2  \t271   \t2.40041\n",
      "5  \t277   \t41930.7\t5  \t1.17166e+07\t0  \t277   \t675393 \t4.39   \t5  \t16 \t2  \t277   \t2.26963\n",
      "6  \t281   \t1935.97\t6  \t153622     \t0  \t281   \t15243.8\t3.82   \t6  \t11 \t2  \t281   \t1.80026\n",
      "7  \t280   \t3173.05\t7  \t157817     \t0  \t280   \t19683.4\t3.75333\t7  \t11 \t3  \t280   \t1.6266 \n",
      "8  \t271   \t82070.3\t8  \t1.23228e+07\t0  \t271   \t978400 \t3.55667\t8  \t9  \t3  \t271   \t1.32921\n",
      "9  \t262   \t3450.12\t9  \t608247     \t0  \t262   \t37143.3\t3.54333\t9  \t9  \t2  \t262   \t1.357  \n",
      "10 \t268   \t43480.5\t10 \t1.18681e+07\t0  \t268   \t684221 \t3.54333\t10 \t9  \t2  \t268   \t1.31711\n",
      "time: 0.6575832366943359\n",
      "   \t      \t                    fitness                    \t                     size                     \n",
      "   \t      \t-----------------------------------------------\t----------------------------------------------\n",
      "gen\tnevals\tavg   \tgen\tmax   \tmin\tnevals\tstd    \tavg \tgen\tmax\tmin\tnevals\tstd   \n",
      "0  \t300   \t2942.7\t0  \t153622\t0  \t300   \t8934.94\t4.09\t0  \t7  \t2  \t300   \t1.6719\n",
      "1  \t262   \t1901.61\t1  \t8356.11\t0  \t262   \t1025.55\t3.94667\t1  \t12 \t2  \t262   \t1.83952\n",
      "2  \t277   \t41471.3\t2  \t1.17166e+07\t0  \t277   \t675250 \t4.14667\t2  \t14 \t2  \t277   \t1.966  \n",
      "3  \t280   \t201062 \t3  \t4.74663e+07\t0  \t280   \t2.81577e+06\t4.42667\t3  \t13 \t2  \t280   \t2.08757\n",
      "4  \t284   \t5486.09\t4  \t155738     \t0  \t284   \t24434.7    \t4.45667\t4  \t14 \t2  \t284   \t2.03669\n",
      "5  \t269   \t46337.1\t5  \t1.24743e+07\t0  \t269   \t719111     \t4.33333\t5  \t13 \t2  \t269   \t1.83182\n",
      "6  \t274   \t45496.5\t6  \t1.23228e+07\t0  \t274   \t710387     \t4.18   \t6  \t13 \t2  \t274   \t2.02178\n",
      "7  \t272   \t2707.94\t7  \t153622     \t0  \t272   \t17614.6    \t3.81   \t7  \t13 \t2  \t272   \t1.6715 \n",
      "8  \t266   \t42912.3\t8  \t1.17166e+07\t0  \t266   \t676183     \t3.4    \t8  \t12 \t2  \t266   \t1.26227\n",
      "9  \t277   \t1822.5 \t9  \t153622     \t0  \t277   \t15249.9    \t3.34333\t9  \t11 \t2  \t277   \t1.12788\n",
      "10 \t273   \t1780.77\t10 \t153622     \t0  \t273   \t15303.7    \t3.36   \t10 \t10 \t2  \t273   \t1.13302\n",
      "time: 0.6205267906188965\n"
     ]
    }
   ],
   "source": [
    "# 慢速评估\n",
    "def evalSymbRegSlow(individual, pset):\n",
    "    # 编译GP树为函数\n",
    "    func = gp.compile(expr=individual, pset=pset)\n",
    "    \n",
    "    # 创建评估数据\n",
    "    xs = [x/5.0 for x in range(-50, 51)]\n",
    "    \n",
    "    # 评估生成的函数并计算MSE\n",
    "    mse = sum((func(x) - x**2)**2 for x in xs) / len(xs)\n",
    "    \n",
    "    return (mse,)\n",
    "\n",
    "toolbox.register(\"evaluate\", evalSymbRegSlow, pset=pset)\n",
    "\n",
    "py_time=[]\n",
    "for i in range(3):\n",
    "    start=time.time()\n",
    "    population = toolbox.population(n=300)\n",
    "    hof = tools.HallOfFame(1)\n",
    "    pop, log  = algorithms.eaSimple(population=population,\n",
    "                               toolbox=toolbox, cxpb=0.9, mutpb=0.1, ngen=10, stats=mstats, halloffame=hof, verbose=True)\n",
    "    end=time.time()\n",
    "    print('time:',end-start)\n",
    "    py_time.append(end-start)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75ed499f209894ae",
   "metadata": {},
   "source": [
    "最后，我们可以使用seaborn绘制一个图来比较Numpy和Python的性能。可以看出，Numpy显著提高了速度。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "f09f85635ed36092",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2023-11-14T09:24:29.905469100Z",
     "start_time": "2023-11-14T09:24:29.810538800Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": "<Figure size 400x300 with 1 Axes>",
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "data = pd.DataFrame({'Category': ['Numpy'] * len(np_time) + ['Python'] * len(py_time),\n",
    "                     'Time': np.concatenate([np_time, py_time])})\n",
    "\n",
    "\n",
    "plt.figure(figsize=(4, 3))\n",
    "sns.set_style(\"whitegrid\")\n",
    "sns.boxplot(data=data, x='Category', y='Time',palette=\"Set3\", width=0.4)\n",
    "plt.title('Comparison of Numpy and Python')\n",
    "plt.xlabel('')\n",
    "plt.ylabel('Time')\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
